#  Copyright (c) 2024. hallele, Inc All rights reserved.

import os
import random
import re
import shutil
import socket
import subprocess
import sys
from time import sleep

import docker

# 必须是非docker环境下安装CI CD工具
client = docker.from_env(use_ssh_client=False)
#  vim /etc/systemd/system/docker.service.d/tcp.conf
# client = docker.DockerClient(base_url='tcp://hallele.cn:2375')

docker_base_dir = r'/home/app/docker'
base_dir = r'/home/app/docker'
# base_dir = r'C:/home/app/docker'
is_update = False


def run_mysql():
    server_name = 'mysql'
    stop_container(server_name)
    copytree('deploy/docker/mysql', fr'{base_dir}/{server_name}')

    mysql_password = 'hallele2024'
    client.containers.run(
        image='mysql:9.0.1',
        name=server_name,
        ports={'3306': 13306},
        restart_policy={'Name': 'always'},
        environment={
            'MYSQL_ROOT_PASSWORD': 'hallele2024',
            'TZ': 'Asia/Shanghai'
        },
        volumes=[
            f'{docker_base_dir}/{server_name}/data:/var/lib/mysql',
            f'{docker_base_dir}/{server_name}/logs:/var/log/mysql',
            f'{docker_base_dir}/{server_name}/initdb:/docker-entrypoint-initdb.d'
        ],
        command='--character-set-server=utf8mb4 --collation-server=utf8mb4_general_ci',
        detach=True,
        stdin_open=True,
    )
    print(f"[{server_name}]: server create successful, root pass is {mysql_password}, pls change it")
    print(f"[{server_name}]: database hallele pass is hallele2024")
    print(f"[{server_name}]: database nacos pass is nacos2024")
    print(f"[{server_name}]: database sonarqube pass is sonarqube2024")


def run_redis():
    server_name = 'redis'
    stop_container(server_name)
    copytree('deploy/docker/redis', fr'{base_dir}/{server_name}')
    password = 'redis2024'
    client.containers.run(
        image='redis:7.4.0',
        name=server_name,
        ports={'6379': 16379},
        restart_policy={'Name': 'always'},
        volumes=[
            f'{docker_base_dir}/{server_name}/config:/etc/redis/'
        ],
        command='redis-server /etc/redis/redis.conf',
        detach=True,
        stdin_open=True,
    )
    print(f"[{server_name}]: server create successful, pass is {password}, pls change it")


def run_nacos():
    server_name = 'nacos'
    if check_pre_server(server_name, 'mysql') == -1:
        return
    password = 'nacos2024'
    stop_container(server_name)
    client.containers.run(
        image='nacos/nacos-server:v2.4.1',
        name=server_name,
        ports={
            '8848': 8848,
            '9848': 9848,
            '9555': 9555
        },
        restart_policy={'Name': 'always'},
        volumes=[
            f'{docker_base_dir}/{server_name}/logs:/home/nacos/logs'
        ],
        environment={
            'MODE': 'standalone',
            'PREFER_HOST_MODE': 'hostname',
            'SPRING_DATASOURCE_PLATFORM': 'mysql',
            'MYSQL_SERVICE_HOST': 'mysql',
            'MYSQL_SERVICE_PORT': 3306,
            'MYSQL_SERVICE_DB_NAME': 'nacos',
            'MYSQL_SERVICE_USER': 'nacos',
            'MYSQL_SERVICE_PASSWORD': 'nacos2024',
            'MYSQL_DATABASE_NUM': 1,
            'MYSQL_SERVICE_DB_PARAM': 'characterEncoding=utf8&connectTimeout=10000&socketTimeout=30000&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true',
            'NACOS_AUTH_ENABLE': 'true',
            'NACOS_AUTH_IDENTITY_KEY': 'nacos',
            'NACOS_AUTH_IDENTITY_VALUE': password,
            'NACOS_AUTH_TOKEN': 'YmJ0c2JidHNiYnRzYmJ0c2JidHNiYnRzYmJ0c2JidHMKYmJ0c2JidHNiYnRzYmJ0c2JidHNiYnRzYmJ0c2JidHMK',
        },
        links={
            'mysql': 'mysql'
        },
        detach=True,
        stdin_open=True,
    )
    print(f"[{server_name}]: server create successful, user nacos pass is {password}, pls change it")


def run_rabbitmq():
    server_name = 'rabbitmq'
    stop_container(server_name)
    copytree('deploy/docker/rabbitmq', fr'{base_dir}/{server_name}')
    password = 'rabbitmq2024'
    client.containers.run(
        image='rabbitmq:3.13.6-management',
        name=server_name,
        ports={'5672': 5672, '15672': 15672},
        restart_policy={'Name': 'always'},
        volumes=[
            f'{docker_base_dir}/{server_name}/config:/etc/rabbitmq',
            f'{docker_base_dir}/{server_name}/data:/var/lib/rabbitmq/mnesia'
        ],
        environment={
            'RABBITMQ_DEFAULT_USER': 'rabbitmq',
            'RABBITMQ_DEFAULT_PASS': password,
        },
        detach=True,
        stdin_open=True,
    )
    print(f"[{server_name}]: server create successful, user rabbitmq pass is {password}, pls change it")


def run_kafka():
    server_name = 'kafka'
    stop_container(server_name)
    copytree('deploy/docker/kafka', fr'{base_dir}/{server_name}')
    if not (os.path.exists(fr'{base_dir}/{server_name}/config/certs/server.keystore.jks') and os.path.exists(
            fr'{base_dir}/{server_name}/config/certs/server.truststore.jks')):
        print(f"[{server_name}]: server create failed, keystore files not find,")
        return
    password = 'kafka2024'
    client.containers.run(
        image='bitnami/kafka:3.8.0',
        name=server_name,
        ports={'9092': 9092},
        restart_policy={'Name': 'always'},
        volumes=[
            f'{docker_base_dir}/{server_name}/config/certs/server.keystore.jks:/opt/bitnami/kafka/config/certs/kafka.keystore.jks:ro',
            f'{docker_base_dir}/{server_name}/config/certs/server.truststore.jks:/opt/bitnami/kafka/config/certs/kafka.truststore.jks:ro'
        ],
        environment={
            'KAFKA_ENABLE_KRAFT': 'true',
            'KAFKA_CFG_NODE_ID': 0,
            'KAFKA_KRAFT_CLUSTER_ID': 'Uu3NlUZ5TBaUEL-Ld8hC1g',
            'KAFKA_CFG_PROCESS_ROLES': 'controller,broker',
            'KAFKA_CFG_CONTROLLER_QUORUM_VOTERS': '0@localhost:9093',
            'KAFKA_CFG_LISTENERS': 'SASL_SSL://0.0.0.0:9092,CONTROLLER://:9093',
            'KAFKA_CFG_ADVERTISED_LISTENERS': 'SASL_SSL://kafka:9092',
            'KAFKA_CLIENT_USERS': 'kafka',
            'KAFKA_CLIENT_PASSWORDS': password,
            'KAFKA_CLIENT_LISTENER_NAME': 'SASL_SSL',
            'KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP': 'CONTROLLER:SASL_PLAINTEXT,SASL_SSL:SASL_SSL',
            'KAFKA_CFG_SASL_MECHANISM_CONTROLLER_PROTOCOL': 'PLAIN',
            'KAFKA_CONTROLLER_USER': 'kafka',
            'KAFKA_CONTROLLER_PASSWORD': password,
            'KAFKA_CFG_CONTROLLER_LISTENER_NAMES': 'CONTROLLER',
            'KAFKA_TLS_TYPE': 'JKS',
            'KAFKA_CERTIFICATE_PASSWORD': 'kafka2024',
            'KAFKA_INTER_BROKER_USER': 'kafka',
            'KAFKA_INTER_BROKER_PASSWORD': password,
            'KAFKA_CFG_INTER_BROKER_LISTENER_NAME': 'SASL_SSL',
            'KAFKA_CFG_SASL_MECHANISM_INTER_BROKER_PROTOCOL': 'PLAIN'
        },
        detach=True,
        stdin_open=True,
    )
    print(f"[{server_name}]: server create successful, user kafka pass is {password}, pls change it")


def run_elasticsearch():
    server_name = 'elasticsearch'
    stop_container(server_name)
    copytree('deploy/docker/elasticsearch', fr'{base_dir}/{server_name}')
    password = 'elasticsearch2024'
    client.containers.run(
        image='elasticsearch:8.14.3',
        name=server_name,
        ports={'9200': 9200, '9300': 9300},
        restart_policy={'Name': 'always'},
        volumes=[
            f'{docker_base_dir}/{server_name}/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml',
            f'{docker_base_dir}/{server_name}/config/ca:/usr/share/elasticsearch/config/certs',
            f'{docker_base_dir}/{server_name}/data:/usr/share/elasticsearch/data',
            f'{docker_base_dir}/{server_name}/logs:/usr/share/elasticsearch/logs',
            f'{docker_base_dir}/{server_name}/set_pass.sh:/usr/share/elasticsearch/set_pass.sh',
        ],
        environment={
            'discovery.type': 'single-node',
            'TZ': 'Asia/Shanghai',
            'bootstrap.memory_lock': 'true',
            'ES_JAVA_OPTS': '-Xms1g -Xmx4g',
            'ELASTIC_PASSWORD': password,
            'KIBANA_PASSWORD': 'kibana2024',
            'TAKE_FILE_OWNERSHIP': 'true',
        },
        detach=True,
        stdin_open=True,
    )

    mod_777(fr'{base_dir}/{server_name}')
    es = client.containers.get(server_name)
    ti = 0
    while True:
        res = es.exec_run('sh /usr/share/elasticsearch/set_pass.sh')
        print(f"[{server_name}]: {res}")
        if res.exit_code == 0:
            break
        ti += 1
        sleep(2)
        if ti > 15:
            stop_container(server_name)
            print(f"[{server_name}]: server create failed")
            return
    print(
        f"[{server_name}]: server create successful, user elastic pass is {password}, pls change it")
    print(f"[{server_name}]: user apm_system pass is apm2024, pls change it")
    print(f"[{server_name}]: user kibana_system pass is kibana2024, pls change it")
    print(f"[{server_name}]: user logstash_system pass is logstash2024, pls change it")
    print(f"[{server_name}]: user beats_system pass is beats2024, pls change it")
    print(f"[{server_name}]: user remote_monitoring_user pass is remote2024, pls change it")


def run_kibana():
    server_name = 'kibana'
    if check_pre_server(server_name, 'elasticsearch') == -1:
        return
    stop_container(server_name)
    copytree('deploy/docker/kibana', fr'{base_dir}/{server_name}')
    client.containers.run(
        image='kibana:8.14.3',
        name=server_name,
        ports={'5601': 15601},
        restart_policy={'Name': 'always'},
        volumes=[
            f'{docker_base_dir}/{server_name}/config:/usr/share/kibana/config'
        ],
        environment={
            'TZ': 'Asia/Shanghai'
        },
        links={
            'elasticsearch': 'elasticsearch'
        },
        detach=True,
        stdin_open=True,
    )

    print(
        f"[{server_name}]: server create successful, user elastic pass is elasticsearch2024, pls change it")


def run_logstash():
    server_name = 'logstash'
    if check_pre_server(server_name, 'elasticsearch') == -1:
        return
    stop_container(server_name)
    copytree('deploy/docker/logstash', fr'{base_dir}/{server_name}')
    client.containers.run(
        image='logstash:8.14.3',
        name=server_name,
        ports={'5044': 15044},
        restart_policy={'Name': 'always'},
        volumes=[
            f'{docker_base_dir}/{server_name}/config:/usr/share/logstash/config',
            f'{docker_base_dir}/{server_name}/data:/usr/share/logstash/data',
            f'{docker_base_dir}/{server_name}/pipeline:/usr/share/logstash/pipeline'
        ],
        environment={
            'TZ': 'Asia/Shanghai'
        },
        links={
            'elasticsearch': 'elasticsearch'
        },
        detach=True,
        stdin_open=True,
    )
    mod_777(fr'{base_dir}/{server_name}')

    print(
        f"[{server_name}]: server create successful, ")


def run_skywalking():
    server_name = 'skywalking'
    if check_pre_server(server_name, 'elasticsearch') == -1:
        return
    stop_container(server_name)
    copytree('deploy/docker/skywalking', fr'{base_dir}/{server_name}')
    client.containers.run(
        image='apache/skywalking-oap-server:10.0.1',
        name=server_name,
        ports={'11800': 11800, '12800': 12800},
        restart_policy={'Name': 'always'},
        volumes=[
            f'{docker_base_dir}/{server_name}/config/application.yml:/skywalking/config/application.yml',
            f'{docker_base_dir}/{server_name}/config/ca/:/skywalking/config/ca',
        ],
        environment={
            'TZ': 'Asia/Shanghai',
            'SW_STORAGE': 'elasticsearch',
            'SW_STORAGE_ES_CLUSTER_NODES': 'elasticsearch:9200',
            'SW_STORAGE_ES_HTTP_PROTOCOL': 'http',
            'SW_ES_USER': 'elastic',
            'SW_ES_PASSWORD': 'elasticsearch2024'
        },
        links={
            'elasticsearch': 'elasticsearch'
        },
        detach=True,
        stdin_open=True
    )

    print(f"[{server_name}]: server create successful")


def run_skywalkingui():
    server_name = 'skywalking-ui'
    if check_pre_server(server_name, 'skywalking') == -1:
        return
    stop_container(server_name)
    copytree('deploy/docker/skywalking', fr'{base_dir}/{server_name}')
    client.containers.run(
        image='apache/skywalking-ui:10.0.1',
        name=server_name,
        ports={'8080': 11801},
        restart_policy={'Name': 'always'},
        environment={
            'TZ': 'Asia/Shanghai',
            'SW_OAP_ADDRESS': 'http://skywalking:12800'
        },
        links={
            'skywalking': 'skywalking'
        },
        detach=True,
        stdin_open=True
    )

    print(f"[{server_name}]: server create successful")


def run_minio():
    server_name = 'minio'
    if check_pre_server(server_name, 'kafka') == -1:
        return
    stop_container(server_name)
    # copytree('deploy/docker/minio', fr'{win_base_dir}/{server_name}')
    password = "minio2024"
    client.containers.run(
        image='bitnami/minio:2024.8.29',
        name=server_name,
        ports={'9000': 19000, '9001': 19001},
        restart_policy={'Name': 'always'},
        volumes=[
            f'{docker_base_dir}/{server_name}/data:/bitnami/minio/data',
        ],
        environment={
            'MINIO_ROOT_USER': 'minio',
            'MINIO_ROOT_PASSWORD': password
        },
        links={
            'kafka': 'kafka'
        },
        detach=True,
        stdin_open=True
    )
    mod_777(fr'{base_dir}/{server_name}')

    print(f"[{server_name}]: server create successful, user minio pass is {password}, pls change it")


def run_postgres():
    server_name = 'postgres'
    stop_container(server_name)
    copytree('deploy/docker/postgres', fr'{base_dir}/{server_name}')
    password = "postgres2024"
    client.containers.run(
        image='postgres:13.16',
        name=server_name,
        ports={'5432': 15432},
        restart_policy={'Name': 'always'},
        volumes=[
            f'{docker_base_dir}/{server_name}/data:/var/lib/postgresql/data',
            f'{docker_base_dir}/{server_name}/initdb:/docker-entrypoint-initdb.d'
        ],
        environment={
            'POSTGRES_PASSWORD': password
        },
        detach=True,
        stdin_open=True
    )

    print(f"[{server_name}]: server create successful, user postgres pass is {password}, pls change it")


def run_sentinel():
    server_name = 'sentinel'
    if check_pre_server(server_name, 'nacos') == -1:
        return
    stop_container(server_name)
    # copytree('deploy/docker/sentinel', fr'{win_base_dir}/{server_name}')
    password = 'sentinel2024'
    client.containers.run(
        image='bladex/sentinel-dashboard:1.8.7',
        name=server_name,
        ports={'8858': 18858, '8719': 18719},
        restart_policy={'Name': 'always'},
        volumes=[
        ],
        environment={
            'sentinel.dashboard.auth.username': 'sentinel',
            'sentinel.dashboard.auth.password': password
        },
        links={
            'nacos': 'nacos'
        },
        detach=True,
        stdin_open=True
    )

    print(f"[{server_name}]: server create successful, user sentinel pass is {password}, pls change it")


def run_sonarqube():
    server_name = 'sonarqube'
    if check_pre_server(server_name, 'postgres') == -1:
        return
    stop_container(server_name)
    copytree('deploy/docker/sonarqube', fr'{base_dir}/{server_name}')
    client.containers.run(
        image='sonarqube:9.9.6-community',
        name=server_name,
        ports={'9000': 19003},
        restart_policy={'Name': 'always'},
        volumes=[
            f'{docker_base_dir}/{server_name}/conf:/opt/sonarqube/conf',
            f'{docker_base_dir}/{server_name}/logs:/opt/sonarqube/logs',
            f'{docker_base_dir}/{server_name}/extensions:/opt/sonarqube/extensions',
        ],
        environment={
            'SONAR_ES_BOOTSTRAP_CHECKS_DISABLE': 'true',
        },
        links={
            'postgres': 'postgres'
        },
        detach=True,
        stdin_open=True
    )

    print(f"[{server_name}]: server create successful, user admin pass is admin, pls change it")


def run_prometheus():
    server_name = 'prometheus'
    stop_container(server_name)
    copytree('deploy/docker/prometheus', fr'{base_dir}/{server_name}')
    client.containers.run(
        image='bitnami/prometheus:2.54.0',
        name=server_name,
        ports={'9090': 19090},
        restart_policy={'Name': 'always'},
        volumes=[
            f'{docker_base_dir}/{server_name}/conf/prometheus.yml:/opt/bitnami/prometheus/conf/prometheus.yml',
            f'{docker_base_dir}/{server_name}/data:/opt/bitnami/prometheus/data'
        ],
        environment={
            'SONAR_ES_BOOTSTRAP_CHECKS_DISABLE': 'true',
        },
        links={
            'mysql': 'mysql'
        },
        detach=True,
        stdin_open=True
    )

    print(f"[{server_name}]: server create successful")


def run_seata():
    server_name = 'seata'
    check_pre_server(server_name, 'nacos')
    stop_container(server_name)
    copytree('deploy/docker/seata', fr'{base_dir}/{server_name}')
    client.containers.run(
        image='apache/seata-server:2.1.0',
        name=server_name,
        ports={'8091': 18091, '7091': 17091},
        restart_policy={'Name': 'always'},
        volumes=[
            f'{docker_base_dir}/{server_name}/conf/application.yml:/seata-server/resources/application.yml',
            f'{docker_base_dir}/{server_name}/sessionStore:/seata-server/sessionStore'
        ],
        environment={
            'STORE_MODE': 'file',
        },
        links={
            'nacos': 'nacos'
        },
        detach=True,
        stdin_open=True
    )

    print(f"[{server_name}]: server create successful, user seata pass is seata2024, pls change it")


def check_pre_server(current, server_name):
    retry_times = 0
    while True:
        status = get_container_status(server_name)
        print(f"[{current}]: check {server_name} server, status: {status}")
        if status == 'running':
            return 0
        retry_times += 1
        if retry_times >= 5:
            print(f"[{current}]: server create failed, because {server_name} server is not running.")
            return -1
        sleep(2)


def stop_container(name):
    for container in client.containers.list(True):
        if container.name == name:
            print(f'[{name}]: Container {name} status: {container.status}')
            if container.status != 'exited':
                container.stop()
                print(f"[{name}]: Stop container {name}")
            container.remove()
            print(f"[{name}]: Remove container {name}")
            return
    print(f'[{name}]: Can not find container {name}, ignore it.')


def get_container_status(name):
    for container in client.containers.list():
        if container.name == name:
            return container.status


def copytree(ori, dst):
    if is_update:
        return
    if os.path.exists(dst):
        shutil.rmtree(dst)
    shutil.copytree(ori, dst)


def mod_777(path):
    cmd = ['chmod', "-R", "777", path]
    subprocess.run(cmd, universal_newlines=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=False)


def change_file_content(ori_file, dst_file, rep_words):
    with open(ori_file, 'r', encoding='utf-8') as ori, open(dst_file, 'w', encoding='utf-8') as dst:
        for line in ori:
            for key, value in rep_words.items():
                line = re.sub(key, value, line)
            dst.write(line)
    os.remove(ori_file)


def generic_random_pass(randomlength):
    """
      生成一个指定长度的随机字符串
    """
    random_str = ''
    base_str = 'ABCDEFGHIGKLMNOPQRSTUVWXYZabcdefghigklmnopqrstuvwxyz0123456789'
    length = len(base_str) - 1
    for i in range(randomlength):
        random_str += base_str[random.randint(0, length)]
    return random_str


if __name__ == '__main__':
    commands = {
        'mysql': run_mysql,
        'redis': run_redis,
        'nacos': run_nacos,
        'rabbitmq': run_rabbitmq,
        'kafka': run_kafka,
        'elasticsearch': run_elasticsearch,
        'kibana': run_kibana,
        'logstash': run_logstash,
        'skywalking': run_skywalking,
        'skywalkingui': run_skywalkingui,
        'minio': run_minio,
        'postgres': run_postgres,
        'sentinel': run_sentinel,
        'sonarqube': run_sonarqube,
        'prometheus': run_prometheus,
        'seata': run_seata,
    }
    args = sys.argv
    if '-u' in args:
        is_update = True
        print("Update all services.")
    else:
        for arg in args:
            if arg in commands:
                if os.path.exists(f'{base_dir}/{arg}'):
                    shutil.rmtree(f'{base_dir}/{arg}')
                commands[arg]()
